/*
 * Package il.ac.biu.cs.grossmm.test.fooAdapters
 * File PrivacyFilter.java
 * 
 * This is a part of presence service framework API. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package il.ac.biu.cs.grossmm.tests.fooAdapters;

import static il.ac.biu.cs.grossmm.api.data.NodeTypeByInterface.*;
import static il.ac.biu.cs.grossmm.api.keys.KeyFactory.*;
import static il.ac.biu.cs.grossmm.api.keys.PatternFactory.*;
import static il.ac.biu.cs.grossmm.api.presence.BaseVocabulary.*;
import il.ac.biu.cs.grossmm.api.OperationFailedException;
import il.ac.biu.cs.grossmm.api.data.Node;
import il.ac.biu.cs.grossmm.api.data.Root;
import il.ac.biu.cs.grossmm.api.data.UpdateType;
import il.ac.biu.cs.grossmm.api.flow.*;
import il.ac.biu.cs.grossmm.api.keys.Key;
import il.ac.biu.cs.grossmm.api.keys.KeyPattern;
import il.ac.biu.cs.grossmm.api.presence.Entity;
import il.ac.biu.cs.grossmm.api.presence.Policy;
import il.ac.biu.cs.grossmm.api.presence.ResourceType;
import il.ac.biu.cs.grossmm.api.server.ComponentManager;
import il.ac.biu.cs.grossmm.foo.format.OnlineStatus;
import il.ac.biu.cs.grossmm.foo.format.PersonalRules;
import il.ac.biu.cs.grossmm.foo.format.Presence;
import il.ac.biu.cs.grossmm.foo.format.PresenceTuple;
import il.ac.biu.cs.grossmm.foo.format.ServiceRules;
import il.ac.biu.cs.grossmm.foo.format.WatcherRules;

import java.awt.SplashScreen;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

public class PrivacyFilter extends DataProcessorBase implements
	Notifier<Presence>, Subscriber<Presence> {
	private static Logger logger = Logger.getLogger("PrivacyFilter");

//	static {
//		logger.setLevel(Level.FINE);
//	}

	final PolicySubscriber policySubscriber = new PolicySubscriber();

	SubscriptionPoint<Presence> presence_sp;

	SubscriptionPoint<WatcherRules> policy_sp;

	NotificationPoint<Presence> np;

	public PrivacyFilter()
	{
		super("Privacy Filter", false);
	}

	class PolicySubscriber implements Subscriber<WatcherRules> {

		public void activated(Key key, Root<WatcherRules> node)
		{
			Meta meta = metas.get(key.value());
			if (meta == null)
				return;

			meta.policyKey = key;
			meta.policyStatus = Status.ACTIVE;
			meta.activated();
		}

		public void deactivated(Key key, Status status)
			throws NoSuchSubscription
		{
			Meta meta = metas.get(key.value());
			if (meta == null)
				return;

			meta.policyStatus = status;
			meta.deactivated();
		}

		public void notify(Root<WatcherRules> node, NodeEventInspector inspector)
		{
			if( logger.isLoggable(Level.FINE) ) {
				logger.fine(inspector.toString(node));
			}
			
			Meta meta = metas.get(node.getKey().value());
			try {
				meta.processAllRules();
			} catch (OperationFailedException e) {
				e.printStackTrace();
			}
		}

	}

	class PersonFilter {
		Root<Presence> root = null;
		
		boolean firstApply = true;

		boolean allowAccess;

		boolean showActivities;

		boolean showLocation;

		boolean showMood;

		boolean showSphere;

		/**
		 * @param ident
		 * @param rroot
		 * @param rins
		 * @param sphere
		 * @throws OperationFailedException
		 */
		private void rebuild(String ident, Root<WatcherRules> rroot,
			NodeInspector rins, String sphere) throws OperationFailedException
		{
			logger.fine(ident);

			Set<Node<PersonalRules>> prs =
				rins.getSubnodes(rroot, WatcherRules.PERSONAL_RULES);

			allowAccess = false;
			showActivities = true;
			showLocation = true;
			showMood = true;
			showSphere = true;
			
			for (Node<PersonalRules> pr : prs) {
				String ident2 = rins.getValue(pr, PersonalRules.WATCHER);
				if (ident2 != null && !ident2.equals(ident))
					continue;

				String sphere2 = rins.getValue(pr, PersonalRules.SPHERE);
				if (sphere2 != null && !sphere2.equals(sphere))
					continue;

				if (rins.getValue(pr, PersonalRules.ALLOW_ACCESS)) {
					allowAccess = true;

					if (showActivities)
						showActivities =
							rins.getValue(pr, PersonalRules.SHOW_ACTIVITIES);

					if (showLocation)
						showLocation =
							rins.getValue(pr, PersonalRules.SHOW_LOCATION);

					if (showMood)
						showMood = rins.getValue(pr, PersonalRules.SHOW_MOOD);

					if (showSphere)
						showSphere =
							rins.getValue(pr, PersonalRules.SHOW_SPHERE);
				} else {
					System.out.println("NOT ALLOWED.");
					allowAccess = false;
					showActivities = false;
					showLocation = false;
					showMood = false;
					showSphere = false;
					break;
				}
			}
		}

		public synchronized  NodeManipulator apply(Key watcherKey, Root<Presence> proot,
			NodeInspector pins) throws OperationFailedException
		{
			logger.fine(allowAccess ? "allow" : "deny");

			if (!allowAccess) {
				if (root != null || firstApply ) {
					logger.fine("deactivating");
					np.deactivated(watcherKey, Status.FORBIDDEN);
					root = null;
				}
				return null; // access not allowed
			}

			if (root == null) {
				logger.fine("activating");
				root = np.activated(watcherKey);
			}

			NodeManipulator man = np.writeLock(root);

			man.setAndCheck(root, Presence.ENTITY, pins.getValue(proot,
				Presence.ENTITY));

			man.setAndCheck(root, Presence.ACTIVITIES, showActivities
				? pins.getValue(proot, Presence.ACTIVITIES) : null);

			man.setAndCheck(root, Presence.LOCATION, showLocation
				? pins.getValue(proot, Presence.LOCATION) : null);

			man.setAndCheck(root, Presence.MOOD, showMood ? pins.getValue(
				proot, Presence.MOOD) : null);

			man.setAndCheck(root, Presence.SPHERE, showSphere ? pins.getValue(
				proot, Presence.SPHERE) : null);

			// np.writeUnlock(root);

			return man; // activated
		}
	}

	class ServiceFilter {
		Node<PresenceTuple> node;

		boolean validRule = false;

		boolean showCapabilities = true;

		boolean showClass = true;

		boolean showPresence = true;

		/**
		 * @param ident
		 * @param rroot
		 * @param rins
		 * @param sphere
		 * @throws OperationFailedException
		 */
		private void rebuild(String ident, Root<WatcherRules> rroot,
			NodeInspector rins, String sphere, String tupleClass)
			throws OperationFailedException
		{
			logger.fine(ident);

			Set<Node<ServiceRules>> srs =
				rins.getSubnodes(rroot, WatcherRules.SERVICE_RULES);

			validRule = false;
			showCapabilities = true;
			showClass = true;
			showPresence = true;

			for (Node<ServiceRules> sr : srs) {
				String ident2 = rins.getValue(sr, ServiceRules.WATCHER);
				if (ident2 != null && !ident2.equals(ident))
					continue;

				String sphere2 = rins.getValue(sr, ServiceRules.SPHERE);
				if (sphere2 != null && !sphere2.equals(sphere))
					continue;

				String class2 = rins.getValue(sr, ServiceRules.CLASS);
				if (class2 != null && !class2.equals(tupleClass))
					continue;

				validRule = true;

				if (showCapabilities)
					showCapabilities =
						rins.getValue(sr, ServiceRules.SHOW_CAPS);

				if (showClass)
					showClass = rins.getValue(sr, ServiceRules.SHOW_CLASS);

				if (showPresence)
					showPresence =
						rins.getValue(sr, ServiceRules.SHOW_PRESENCE);
			}
		}

		boolean apply(Key watcherKey, Node<PresenceTuple> tuple,
			NodeInspector pins, NodeManipulator man)
			throws OperationFailedException
		{
			logger.fine("valid: " + validRule + " "
				+ (node == null ? "null" : "not null"));

			boolean ownLock = man == null;

			if (!validRule) {
				if (node != null) {
					if (ownLock)
						man = np.writeLock(node);

					logger.fine("removing tuple");
					man.removeSubnode(node);

					if (ownLock)
						np.writeUnlock(node);

					node = null;
				}

				return false;
			}

			if (ownLock)
				man = np.writeLock(node);

			if (node == null) {
				logger.fine("adding tuple");
				node =
					man.createSubnode(np.getRoot(watcherKey), Presence.TUPLES);
				man.setValue(node, PresenceTuple.ID, pins.getValue(tuple,
					PresenceTuple.ID));
			}

			man.setAndCheck(node, PresenceTuple.PRIORITY, pins.getValue(tuple,
				PresenceTuple.PRIORITY));

			man.setAndCheck(node, PresenceTuple.ADDRESS, pins.getValue(tuple,
				PresenceTuple.ADDRESS));

			man.setAndCheck(node, PresenceTuple.CAPABILITIES, showCapabilities
				? pins.getValue(tuple, PresenceTuple.CAPABILITIES) : null);

			man.setAndCheck(node, PresenceTuple.CLASS, showClass
				? pins.getValue(tuple, PresenceTuple.CLASS) : null);

			man.setAndCheck(node, PresenceTuple.STATUS, showPresence
				? pins.getValue(tuple, PresenceTuple.STATUS)
				: OnlineStatus.OFFLINE);

			if (ownLock)
				np.writeUnlock(node);

			return true;

		}
	}

	// person-filters for given presentity and susbcribed watchers,
	// indexed by watcher keys
//	class PersonGroup extends HashMap<Key, PersonFilter> {
//		private static final long serialVersionUID = 8103773240637289072L;
//	}

	// service-filters for given presentity and susbcribed watchers,
	// indexed by watcher keys
	class ServiceGroup extends HashMap<Key, ServiceFilter> {
		private static final long serialVersionUID = 6505186895277498890L;
	}

	// status of backend subscriptions
	// together with keys of all incomping susbcriptions
	class Meta extends HashMap<Key, PersonFilter> {
		
		private static final long serialVersionUID = 2255474299405931615L;

		public Meta(Key key)
		{
			Entity presentity = (Entity) key.value();

			policyKey =
				key(presentity, entry(RESOURCE_TYPE,
					Policy.policy("foo-watcher-rules")));

			presenceKey =
				key(presentity, entry(RESOURCE_TYPE, ResourceType.PRESENCE));
			
			put(key, new PersonFilter());
		}

		// null -> not subscribed
		Status policyStatus;

		Key policyKey;

		// null => not subscribed
		Status presenceStatus;

		Key presenceKey;

		Map<Node<PresenceTuple>, ServiceGroup> serviceGroups =
			new HashMap<Node<PresenceTuple>, ServiceGroup>();

		private Status status;

		void processRules(Key key, Node<PresenceTuple> t)
			throws OperationFailedException
		{
			Entity watcher = (Entity) key.value(WATCHER);

			String ident = watcher.getIdentifiers().iterator().next();

			Root<WatcherRules> rroot = policy_sp.getRoot(policyKey);
			Root<Presence> proot = presence_sp.getRoot(presenceKey);

			NodeInspector rins = policy_sp.readLock(rroot);
			NodeInspector pins = presence_sp.readLock(proot);

			String sphere = pins.getValue(proot, Presence.SPHERE);

			if (t == null) {
				// process rules for all tuples, applied to this watcher

				logger.fine("All for: " + ident);

				PersonFilter pf = get(key);
				if (pf == null) {
					pf = new PersonFilter();
					pf.rebuild(ident, rroot, rins, sphere);
					put(key, pf);
				} else
					pf.rebuild(ident, rroot, rins, sphere);
				

				NodeManipulator man = pf.apply(key, proot, pins);
				if (man == null)
					return; // return if access forbidden

				Set<Node<PresenceTuple>> tuples =
					pins.getSubnodes(proot, Presence.TUPLES);

				// all tuples...
				for (Node<PresenceTuple> tuple : tuples) {
					processTuple(key, ident, rins, pins, man, sphere, tuple);
				}

				np.writeUnlock(pf.root);
			} else
				// check if pf is forbidden?
				processTuple(key, ident, rins, pins, null, sphere, t);

			presence_sp.readUnlock(proot);
			policy_sp.readUnlock(rroot);
		}

		/**
		 * @param ident
		 * @param rins
		 * @param pins
		 * @param sphere
		 * @param tuple
		 * @throws OperationFailedException
		 */
		private void processTuple(Key watcherKey, String ident,
			NodeInspector rins, NodeInspector pins, NodeManipulator man,
			String sphere, Node<PresenceTuple> tuple)
			throws OperationFailedException
		{

			logger.fine("Tuple for : " + ident);

			Root<WatcherRules> rroot = policy_sp.getRoot(policyKey);

			String tupleClass = pins.getValue(tuple, PresenceTuple.CLASS);

			ServiceGroup serviceGroup = serviceGroups.get(tuple);

			if (serviceGroup == null) {
				serviceGroup = new ServiceGroup();
				serviceGroups.put(tuple, serviceGroup);
			}

			ServiceFilter sf = serviceGroup.get(watcherKey);
			if (sf == null) {
				logger.fine("New filter");
				sf = new ServiceFilter();
				serviceGroup.put(watcherKey, sf);
			}

			sf.rebuild(ident, rroot, rins, sphere, tupleClass);

			if (!sf.apply(watcherKey, tuple, pins, man)) {
				// no rule can be derived
				serviceGroup.remove(watcherKey);

				// are there any rules for this tuple for other watchers?
				if (serviceGroup.isEmpty())
					serviceGroups.remove(tupleClass);
			}
		}

		void processRules(Node<PresenceTuple> tuple)
			throws OperationFailedException
		{
			logger.fine("");
			for (Key key : keySet()) {
				processRules(key, tuple);
			}
		}

		void processAllRules() throws OperationFailedException
		{
			logger.fine("");
			for (Key key : keySet()) {
				processRules(key, null);
			}
		}

		public void activated()
		{
//			if (presenceStatus == null || policyStatus == null)
//				return;
			
			if( presenceStatus == Status.ACTIVATING 
				|| policyStatus == Status.ACTIVATING )
				return;

			if (presenceStatus == Status.ACTIVE
				&& policyStatus == Status.ACTIVE) {
				if (status == Status.ACTIVE)
					return;

				try {
					processAllRules();
				} catch (OperationFailedException e) {
					e.printStackTrace();
				}

				status = Status.ACTIVE;

			} else
				deactivated(); // status might change

		}

		public void deactivated()
		{
			if (presenceStatus == null || policyStatus == null)
				return;

			Status newStatus = Status.ERROR;
			if (presenceStatus == Status.NORESOURCE) {
				newStatus = Status.NORESOURCE;
			} else if (presenceStatus != Status.ACTIVE
				|| policyStatus != Status.ACTIVE) {
				newStatus = Status.PENDING;
			}

			if (newStatus != status) {
				status = newStatus;
				for (Key key : keySet())
					try {
						np.deactivated(key, status);
					} catch (NoSuchSubscription e) {
						e.printStackTrace();
					}
			}

			serviceGroups.clear();
		}
	} // Meta

	// indexed by presentity
	Map<Entity, Meta> metas = new HashMap<Entity, Meta>();

	public void fetch(Key key, Track track, Root<Presence> node,
		NodeManipulator manipulator)
	{

		throw new RuntimeException("Not implemented");
	}

	public boolean subscribe(Key key, Track track) throws Exception
	{
		Entity presentity = (Entity) key.value();

		Meta meta = metas.get(presentity);

		if (meta == null) {
			// first subscription for this presentity
			meta = new Meta(key);

			metas.put(presentity, meta);

			Status s = policy_sp.subscribe(meta.policyKey, track);

			// no policy provider?
			if (s == Status.UNMATCHED) {
				metas.remove(presentity);
				return false;
			}

			s = presence_sp.subscribe(meta.presenceKey, track);

			// no presence provider?
			if (s == Status.UNMATCHED) {
				metas.remove(presentity);
				return false;
			}

			// ok
			return true;
		}

		Status status = meta.status;

		if (status != null && status != Status.ACTIVE) {
			np.deactivated(key, status);
			return true;
		}

		meta.processRules(key, null);

		return true;
	}

	public void unsubscribe(Key key)
	{
		Meta meta = metas.get(key.value());

		meta.remove(key);

		if (meta.isEmpty()) {
			policy_sp.unsubscribe(meta.policyKey);
			presence_sp.unsubscribe(meta.presenceKey);
			metas.remove(key.value());
		}
	}

	public void activated(Key key, Root<Presence> node)
	{
		Meta meta = metas.get(key.value());
		meta.presenceStatus = Status.ACTIVE;
		meta.activated();
	}

	public void deactivated(Key key, Status status) throws NoSuchSubscription
	{
		Meta meta = metas.get(key.value());
		meta.presenceStatus = status;
		meta.deactivated();
	}

	public void notify(Root<Presence> node, NodeEventInspector inspector)
	{
		Meta meta = metas.get(node.getKey().value());

		Set<Node<PresenceTuple>> tuples;
		try {
			tuples = inspector.getSubnodes(node, Presence.TUPLES);
		} catch (OperationFailedException e1) {
			e1.printStackTrace();
			return;
		}

		for (Key key : meta.keySet()) {
			PersonFilter pf = meta.get(key);
			NodeManipulator man;
			try {
				man = pf.apply(key, node, inspector);
				if (man == null)
					continue; // forbidden

				for (Node<PresenceTuple> tuple : tuples) {

					ServiceGroup group = meta.serviceGroups.get(tuple);
					
//					if (inspector.getUpdateType(tuple) == UpdateType.ADDED) {
					if( group == null ) {
						meta.processTuple(key, processorName, inspector,
							inspector, man, processorName, tuple);
						continue;
					}

					// ServiceGroup group = meta.serviceGroups.get(tuple);
					ServiceFilter filter = group.get(key);

					if (filter == null)
						// this tuple is filtered out for this wtcher
						continue;

					if (inspector.getUpdateType(tuple) == UpdateType.REMOVED) {
						if (filter.node != null)
							man.removeSubnode(filter.node);

					} else
						filter.apply(key, tuple, inspector, man);
				}

				np.writeUnlock(pf.root);

				tuples =
					inspector.getSubnodes(node, Presence.TUPLES,
						UpdateType.REMOVED);
				for (Node<PresenceTuple> tuple : tuples) {
					meta.serviceGroups.remove(tuple);
				}

			} catch (OperationFailedException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public void setComponentManager(ComponentManager container)
		throws Exception
	{
		super.setComponentManager(container);

		KeyPattern pattern1 =
			pattern(Entity.class, mandatory(RESOURCE_TYPE,
				ResourceType.PRESENCE), mandatory(WATCHER, Entity.class));

		KeyPattern pattern2 =
			pattern(Entity.class, mandatory(RESOURCE_TYPE,
				Policy.policy("foo-watcher-rules")));

		KeyPattern pattern3 =
			pattern(Entity.class, mandatory(RESOURCE_TYPE,
				ResourceType.PRESENCE));

		np =
			adm.createNotificationPoint(this, pattern1,
				nodeType(Presence.class), 1);

		policy_sp =
			adm.createSubscriptionPoint(policySubscriber, pattern2,
				nodeType(WatcherRules.class));

		presence_sp =
			adm.createSubscriptionPoint(this, pattern3,
				nodeType(Presence.class));

	}
}
